'use strick';
(function(host, name){
	
var util = '$$$util$$$' in host ? host['$$$util$$$'] : require('./libra.util');

function Model(data){
	this.id = util.cache(name, this);
	this.data = {};
	this.getter = {};
	this._ctrls = [];
	this._ctrls_key = {};

	if(data && util.isObject(data)){
		this.put(data, 1);
	}
	if(data && util.isArray(data)){
		var _ = this;
		data.forEach(function(key){
			_.data[key] = undefined;
		});
	}
}
Model.prototype = {
	each: function(fn){
		for(var p in this.data){
			if(fn(this.get(p), p)) return;
		}
	},
	get: function(key){
		if(util.isString(key)){
			if(arguments.length < 2){
				return util.convert(this.data[key], this.getter[key], key);
			} else {
				return this.get(util.slice(arguments));
			}
		} else if(util.isArray(key)){
			return this.map(key);
		} else {
			return this.get(key + '');
		}
	},
	output: function(){
		var data = {}, _ = this;
		this.each(function(v, p){
			data[p] = _.get(p);
		});
		return data;
	},
	clone: function(){
		var m = new Model(this.output());
		util.each(this.getter, function(v, p){
			m.defineGetter(p, v);
		});
		return m;
	},
	has: function(key){
		return key in this.data;
	},
	getIndexOfListener: function(ctrl, key){
		if(key && util.isString(key)){
			if(key in this._ctrls_key){
				return this._ctrls_key[key].indexOf(ctrl);
			}
			return -1;
		} else {
			return this._ctrls.indexOf(ctrl);
		}
	},
	isListenerKey: function(key){
		return key && util.isString(key);
	},
	bind: function(ctrl, noL){
		var noL = arguments[1];
		if(this.getIndexOfListener(ctrl) < 0){
			this._ctrls.push(ctrl);
			!noL && ctrl.listen(this.output(), null, this);
		}
	},
	bindKey: function(ctrl, key, noL){
		if(this.isListenerKey(key)){
			if(!(key in this._ctrls_key)){
				this._ctrls_key[key] = [];
			}
			if(this.getIndexOfListener(ctrl, key) < 0){
				this._ctrls_key[key].push(ctrl);
				!noL && ctrl.listen(this.get(key), null, key, this);
			}
		}
	},
	unbind: function(ctrl, key){
		if(key && util.isString(key)){
			var idx = this.getIndexOfListener(ctrl, key);
			idx > -1 && this._ctrls_key[key].splice(idx, 1);
		} else {
			var idx = this.getIndexOfListener(ctrl);
			idx > -1 && this.ctrls.splice(idx, 1);
		}
	},
	dump: function(){
		this._ctrls.splice(0, this._ctrls.length);
	},
	put: function(objVal, noTell){
		var keys = Object.keys(objVal);
		var oldVal = this.map(keys);
		var newVal = util.map_callback(objVal, keys, this.getter);
		var diff = util.compair(keys, oldVal, newVal);
		var data = null;
		if(diff.length > 0){
			data = this.data;
			diff.forEach(function(key){ data[key] = newVal[key]; })
			if(!noTell){
				oldVal = util.map(oldVal, diff);
				newVal = util.map(newVal, diff);
				data = this;
				diff.forEach(function(key){
					data.tell(newVal[key], oldVal[key], key);
				})
				this.tell(newVal, oldVal);
			}
		}
	},
	set: function(key, val, noTell){
		var oldVal = this.get(key);
		var newVal = util.convert(val, this.getter[key], key);
		if(oldVal === newVal) return this;

	},
	tell: function(val, oldVal, key){
		var _ = this;
		if(key && util.isString(key) && (key in _._ctrls_key)){
			_._ctrls_key[key].forEach(function(ctrl){
				ctrl.listen(val, oldVal, key, _);
			});
		} else {
			_._ctrls.forEach(function(ctrl){
				ctrl.listen(val, oldVal, _);
			});
		}
	},
	defineGetter: function(key, fn){
		if(util.isArray(key)){
			var _ = this;
			key.forEach(function(k){
				_.defineGetter(k, fn);
			})
		} else {
			this.getter[key] = fn;
			if(!this.has(key)){
				this.data[key] = undefined;
			}
		}
	},
	map: function(keys){
		if(arguments.length < 1){
			return this.output();
		}
		return util.map_callback(this.data, keys, this.getter);
	}
};

Model.create = function(schema, fn){
	var m = new Model(schema);
	if(util.isArray(schema) && util.isFunction(fn)){
		m.defineGetter(schema, fn);
	}
	return m;
};
Model.Class = function(name, construct, protos){
	return util.Class(name, Model, construct, protos);
};

util.regist(host, name, Model);

})(this, 'model');